﻿''' <summary>
''' Provides access to the audio engine and tracks playing audio files.
''' </summary>
''' <remarks></remarks>
Public Class GameAudio
    Protected _AudioPlayers As New AudioPlayerCollection
    Protected _Engine As GameEngine
    Protected _QueuedSounds As New Queue(Of SoundQueueItem)

    Protected _IsPaused As Boolean
    ''' <summary>
    ''' Indicates if all playing audio has been paused.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property IsPaused As Boolean
        Get
            Return _IsPaused
        End Get
    End Property

    Private _MaximumAudioPlayers As Integer = 20
    ''' <summary>
    ''' The maximum number of simultaneous sounds. Each playing sound requires an instance of Media Player,
    ''' so this property is used to throttle the total number of sounds playing at one time.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property MaximumAudioPlayers() As Integer
        Get
            Return _MaximumAudioPlayers
        End Get
        Set(ByVal value As Integer)
            _MaximumAudioPlayers = value
        End Set
    End Property

    ''' <summary>
    ''' Creates a new instance of GameAudio to service the specified GameEngine.
    ''' </summary>
    ''' <param name="engine">The GameEngine to service.</param>
    ''' <remarks></remarks>
    Public Sub New(ByVal engine As GameEngine)
        _Engine = engine
    End Sub

    ''' <summary>
    ''' Returns the AudioPlayer with the specified playerId if it is loaded, otherwise returns nothing (null).
    ''' </summary>
    ''' <param name="playerId">The Guid of the AudioPlayer to return.</param>
    ''' <returns>The AudioPlayer with the specified playerId if loaded, otherwise Nothing (Null).</returns>
    ''' <remarks></remarks>
    Public Function GetPlayer(playerId As Guid) As AudioPlayer
        If _AudioPlayers.Contains(playerId) Then
            Return _AudioPlayers(playerId)
        End If
        Return Nothing
    End Function

    ''' <summary>
    ''' Pauses all playing audio.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub PauseAll()
        For Each player As AudioPlayer In _AudioPlayers
            If player.HasAudio Then
                player.Pause()
            End If
        Next
        _IsPaused = True
    End Sub

    ''' <summary>
    ''' Plays an audio asset, specifing whether or not to play in a loop and returns the Id of the AudioPlayer which will be used.
    ''' </summary>
    ''' <param name="assetName">The name of the audio asset to play.</param>
    ''' <param name="isLoop">True to play the asset as a continuous loop, otherwise false to play the sound until it ends.</param>
    ''' <returns>The playerId of the AudioPlayer that will be used to play this sound.</returns>
    ''' <remarks></remarks>
    Public Function Play(ByVal assetName As String, ByVal isLoop As Boolean) As Guid
        Dim playerId As Guid = Guid.NewGuid
        _QueuedSounds.Enqueue(New SoundQueueItem(assetName, isLoop, playerId))
        Return playerId
    End Function

    ''' <summary>
    ''' Returns true if the asset has been dequeued and its player is loaded.
    ''' This value will continue to return true after a sound effect has finished playing,
    ''' until the AudioPlayer is reused by another sound.
    ''' </summary>
    ''' <param name="playerId">The Guid of the AudioPlayer to look for.</param>
    ''' <returns>True if the AudioPlayer is loaded, otherwise false.</returns>
    ''' <remarks></remarks>
    Public Function IsLoaded(playerId As Guid) As Boolean
        Return _AudioPlayers.Contains(playerId)
    End Function

    ''' <summary>
    ''' Returns true if the specified AudioPlayer is loaded and currently playing.
    ''' </summary>
    ''' <param name="playerId">The Guid of the AudioPlayer to check.</param>
    ''' <returns>True if the AudioPlayer is loaded and player, otherwise false.</returns>
    ''' <remarks></remarks>
    Public Function IsPlaying(playerId As Guid) As Boolean
        If _AudioPlayers.Contains(playerId) Then
            Dim player As AudioPlayer = _AudioPlayers(playerId)
            If player.NaturalDuration.HasTimeSpan Then
                Return player.Position < player.NaturalDuration.TimeSpan
            End If
        End If
        Return False
    End Function

    ''' <summary>
    ''' Resumes playing of all paused AudioPlayers.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub ResumeAll()
        For Each player As AudioPlayer In _AudioPlayers
            If player.HasAudio Then
                player.Play()
            End If
        Next
        _IsPaused = False
    End Sub

    ''' <summary>
    ''' Stops playback on the AudioPlayer specified.
    ''' </summary>
    ''' <param name="playerId">The Guid playerId of the AudioPlayer to stop.</param>
    ''' <remarks></remarks>
    Public Sub [Stop](playerId As Guid)
        If _AudioPlayers.Contains(playerId) Then
            Dim player As AudioPlayer = _AudioPlayers(playerId)
            player.Stop()
            player.Close()
        End If
    End Sub

    ''' <summary>
    ''' Stops playback on all AudioPlayers
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub StopAll()
        For Each player As AudioPlayer In _AudioPlayers
            If player.HasAudio Then
                player.Stop()
                player.Close()
            End If
        Next
    End Sub

    Protected Friend Sub OnUpdate(ByVal e As GameEngineEventArgs)
        For Each player As AudioPlayer In _AudioPlayers
            If player.NaturalDuration.HasTimeSpan Then
                If player.IsLoop AndAlso player.Position >= player.NaturalDuration.TimeSpan Then
                    player.Position = TimeSpan.Zero
                End If
            End If
        Next
        While _QueuedSounds.Count > 0
            Dim sound As SoundQueueItem = _QueuedSounds.Dequeue
            Dim player As AudioPlayer = GetNextAudioPlayer(sound.IsLoop, sound.PlayerId)
            If player IsNot Nothing Then
                Dim asset As Uri = _Engine.CurrentScene.GetAudioAsset(sound.AssetName)
                player.IsLoop = sound.IsLoop
                player.PlayerId = sound.PlayerId
                player.Open(asset)
                player.Play()
            Else
                _QueuedSounds.Enqueue(sound)
                Exit While
            End If
        End While
    End Sub

    Protected Function GetNextAudioPlayer(ByVal isLoop As Boolean, id As Guid) As System.Windows.Media.MediaPlayer
        Dim result As AudioPlayer = Nothing
        For Each player As AudioPlayer In _AudioPlayers
            If player.NaturalDuration.HasTimeSpan Then
                If Not player.IsLoop AndAlso player.Position >= player.NaturalDuration.TimeSpan Then
                    player.Close()
                    player.IsLoop = False
                    result = player
                    Exit For
                End If
            End If
        Next
        If result Is Nothing Then
            If _AudioPlayers.Count < _MaximumAudioPlayers Then
                result = New AudioPlayer
                result.IsLoop = False
                _AudioPlayers.Add(result)
            End If
        End If
        Return result
    End Function

    Protected Structure SoundQueueItem
        Public AssetName As String
        Public IsLoop As Boolean
        Public PlayerId As Guid

        Public Sub New(asset As String, loopSound As Boolean)
            Me.New(asset, loopSound, Guid.NewGuid)
        End Sub

        Public Sub New(asset As String, loopSound As Boolean, id As Guid)
            AssetName = asset
            IsLoop = loopSound
            PlayerId = id
        End Sub
    End Structure
End Class
